/*
 * $Id: TarHeader.java,v 1.6 2010/04/22 18:36:04 navdeep_mahajan Exp $
 *
 * Copyright (C) 2006 General Electric Company. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 *
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */

/*
 ** Authored by Timothy Gerard Endres
 ** <mailto:time@ice.com>  <http://www.ice.com>
 ** 
 ** This work has been placed into the public domain.
 ** You may use this work in any way and for any purpose you wish.
 **
 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
 ** REDISTRIBUTION OF THIS SOFTWARE. 
 ** 
 */

package com.ge.healthcare.autosc.common.util.tar;

import com.ge.healthcare.autosc.common.ASCLogger;

/**
 * This class encapsulates the Tar Entry Header used in Tar Archives. The class
 * also holds a number of tar constants, used mostly in headers.
 */

public class TarHeader extends Object {
	/**
	 * The length of the name field in a header buffer.
	 */
	public static final int NAMELEN = 100;
	/**
	 * The length of the mode field in a header buffer.
	 */
	public static final int MODELEN = 8;
	/**
	 * The length of the user id field in a header buffer.
	 */
	public static final int UIDLEN = 8;
	/**
	 * The length of the group id field in a header buffer.
	 */
	public static final int GIDLEN = 8;
	/**
	 * The length of the checksum field in a header buffer.
	 */
	public static final int CHKSUMLEN = 8;
	/**
	 * The length of the size field in a header buffer.
	 */
	public static final int SIZELEN = 12;
	/**
	 * The length of the magic field in a header buffer.
	 */
	public static final int MAGICLEN = 8;
	/**
	 * The length of the modification time field in a header buffer.
	 */
	public static final int MODTIMELEN = 12;
	/**
	 * The length of the user name field in a header buffer.
	 */
	public static final int UNAMELEN = 32;
	/**
	 * The length of the group name field in a header buffer.
	 */
	public static final int GNAMELEN = 32;
	/**
	 * The length of the devices field in a header buffer.
	 */
	public static final int DEVLEN = 8;

	/**
	 * LF_ constants represent the "link flag" of an entry, or more commonly,
	 * the "entry type". This is the "old way" of indicating a normal file.
	 */
	public static final byte LF_OLDNORM = 0;
	/**
	 * Normal file type.
	 */
	public static final byte LF_NORMAL = (byte) '0';
	/**
	 * Link file type.
	 */
	public static final byte LF_LINK = (byte) '1';
	/**
	 * Symbolic link file type.
	 */
	public static final byte LF_SYMLINK = (byte) '2';
	/**
	 * Character device file type.
	 */
	public static final byte LF_CHR = (byte) '3';
	/**
	 * Block device file type.
	 */
	public static final byte LF_BLK = (byte) '4';
	/**
	 * Directory file type.
	 */
	public static final byte LF_DIR = (byte) '5';
	/**
	 * FIFO (pipe) file type.
	 */
	public static final byte LF_FIFO = (byte) '6';
	/**
	 * Contiguous file type.
	 */
	public static final byte LF_CONTIG = (byte) '7';

	/**
	 * The magic tag representing a POSIX tar archive.
	 */
	public static final String TMAGIC = "ustar";

	/**
	 * The magic tag representing a GNU tar archive.
	 */
	public static final String GNU_TMAGIC = "ustar  ";

	/**
	 * The entry's name.
	 */
	public StringBuffer name;
	/**
	 * The entry's permission mode.
	 */
	public int mode;
	/**
	 * The entry's user id.
	 */
	public int userId;
	/**
	 * The entry's group id.
	 */
	public int groupId;
	/**
	 * The entry's size.
	 */
	public long size;
	/**
	 * The entry's modification time.
	 */
	public long modTime;
	/**
	 * The entry's checksum.
	 */
	public int checkSum;
	/**
	 * The entry's link flag.
	 */
	public byte linkFlag;
	/**
	 * The entry's link name.
	 */
	public StringBuffer linkName;
	/**
	 * The entry's magic tag.
	 */
	public StringBuffer magic;
	/**
	 * The entry's user name.
	 */
	public StringBuffer userName;
	/**
	 * The entry's group name.
	 */
	public StringBuffer groupName;
	/**
	 * The entry's major device number.
	 */
	public int devMajor;
	/**
	 * The entry's minor device number.
	 */
	public int devMinor;

	public TarHeader() {
		this.magic = new StringBuffer(TarHeader.TMAGIC);

		this.name = new StringBuffer();
		this.linkName = new StringBuffer();

		String user = System.getProperty("user.name", "");

		if (user.length() > 31)
			user = user.substring(0, 31);

		this.userId = 0;
		this.groupId = 0;
		this.userName = new StringBuffer(user);
		this.groupName = new StringBuffer("");
	}

	/**
	 * TarHeaders can be cloned.
	 */
	public Object clone() {
		TarHeader hdr = null;

		try {
			hdr = (TarHeader) super.clone();

			hdr.name = (this.name == null) ? null : new StringBuffer(this.name
					.toString());
			hdr.mode = this.mode;
			hdr.userId = this.userId;
			hdr.groupId = this.groupId;
			hdr.size = this.size;
			hdr.modTime = this.modTime;
			hdr.checkSum = this.checkSum;
			hdr.linkFlag = this.linkFlag;
			hdr.linkName = (this.linkName == null) ? null : new StringBuffer(
					this.linkName.toString());
			hdr.magic = (this.magic == null) ? null : new StringBuffer(
					this.magic.toString());
			hdr.userName = (this.userName == null) ? null : new StringBuffer(
					this.userName.toString());
			hdr.groupName = (this.groupName == null) ? null : new StringBuffer(
					this.groupName.toString());
			hdr.devMajor = this.devMajor;
			hdr.devMinor = this.devMinor;
		} catch (CloneNotSupportedException ex) {
			ASCLogger.error(this.getClass(), "clone", "Exception:" + ex.getMessage(), ex);
		}

		return hdr;
	}

	/**
	 * Get the name of this entry.
	 * 
	 * @return Teh entry's name.
	 */
	public String getName() {
		return this.name.toString();
	}

	/**
	 * Parse an octal string from a header buffer. This is used for the file
	 * permission mode value.
	 * 
	 * @param header
	 *            The header buffer from which to parse.
	 * @param offset
	 *            The offset into the buffer from which to parse.
	 * @param length
	 *            The number of header bytes to parse.
	 * @return The long value of the octal string.
	 */
	public static long parseOctal(byte[] header, int offset, int length){
		long result = 0;
		boolean stillPadding = true;

		int end = offset + length;
		for (int i = offset; i < end; ++i) {
			if (header[i] == 0)
				break;

			if (header[i] == (byte) ' ' || header[i] == '0') {
				if (stillPadding)
					continue;

				if (header[i] == (byte) ' ')
					break;
			}

			stillPadding = false;

			result = (result << 3) + (header[i] - '0');
		}

		return result;
	}

	/**
	 * Parse an entry name from a header buffer.
	 * 
	 * @param header
	 *            The header buffer from which to parse.
	 * @param offset
	 *            The offset into the buffer from which to parse.
	 * @param length
	 *            The number of header bytes to parse.
	 * @return The header's entry name.
	 */
	public static StringBuffer parseName(byte[] header, int offset, int length){
		StringBuffer result = new StringBuffer(length);

		int end = offset + length;
		for (int i = offset; i < end; ++i) {
			if (header[i] == 0)
				break;
			result.append((char) header[i]);
		}

		return result;
	}

	/**
	 * Determine the number of bytes in an entry name.
	 * 
	 * @param header
	 *            The header buffer from which to parse.
	 * @param offset
	 *            The offset into the buffer from which to parse.
	 * @param length
	 *            The number of header bytes to parse.
	 * @return The number of bytes in a header's entry name.
	 */
	public static int getNameBytes(StringBuffer name, byte[] buf, int offset,
			int length) {
		int i;

		for (i = 0; i < length && i < name.length(); ++i) {
			buf[offset + i] = (byte) name.charAt(i);
		}

		for (; i < length; ++i) {
			buf[offset + i] = 0;
		}

		return offset + length;
	}

	/**
	 * Parse an octal integer from a header buffer.
	 * 
	 * @param header
	 *            The header buffer from which to parse.
	 * @param offset
	 *            The offset into the buffer from which to parse.
	 * @param length
	 *            The number of header bytes to parse.
	 * @return The integer value of the octal bytes.
	 */
	public static int getOctalBytes(long value, byte[] buf, int offset,
			int length) {
		int idx = length - 1;

		buf[offset + idx] = 0;
		--idx;
		buf[offset + idx] = (byte) ' ';
		--idx;

		if (value == 0) {
			buf[offset + idx] = (byte) '0';
			--idx;
		} else {
			for (long val = value; idx >= 0 && val > 0; --idx) {
				buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));
				val = val >> 3;
			}
		}

		for (; idx >= 0; --idx) {
			buf[offset + idx] = (byte) ' ';
		}

		return offset + length;
	}

	/**
	 * Parse an octal long integer from a header buffer.
	 * 
	 * @param header
	 *            The header buffer from which to parse.
	 * @param offset
	 *            The offset into the buffer from which to parse.
	 * @param length
	 *            The number of header bytes to parse.
	 * @return The long value of the octal bytes.
	 */
	public static int getLongOctalBytes(long value, byte[] buf, int offset,
			int length) {
		byte[] temp = new byte[length + 1];
		TarHeader.getOctalBytes(value, temp, 0, length + 1);
		System.arraycopy(temp, 0, buf, offset, length);
		return offset + length;
	}

	/**
	 * Parse the checksum octal integer from a header buffer.
	 * 
	 * @param header
	 *            The header buffer from which to parse.
	 * @param offset
	 *            The offset into the buffer from which to parse.
	 * @param length
	 *            The number of header bytes to parse.
	 * @return The integer value of the entry's checksum.
	 */
	public static int getCheckSumOctalBytes(long value, byte[] buf, int offset,
			int length) {
		TarHeader.getOctalBytes(value, buf, offset, length);
		buf[offset + length - 1] = (byte) ' ';
		buf[offset + length - 2] = 0;
		return offset + length;
	}

}
